# -*- coding: utf-8 -*-


# cmap2owl -- Helper application to convert from concept maps to OWL ontologies
# Copyright (c) 2008-2013  Rodrigo Rizzi Starr
#  
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#  
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#  
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


'''
This contains classes that are used to build the interface

@author: Rodrigo Rizzi Starr
@copyright: Copyright © 2008-2013 Rodrigo Rizzi Starr
@license: MIT License
@contact: rodrigo.starr@gmail.com
'''


from __future__ import division

import time
import sys

# GTK+ imports
import pygtk
if not sys.platform == 'win32':
    pygtk.require('2.0')
try:
    import gtk
    import gobject
except:
    from log import log
    log.error('gtk or gobject module not found!')
    sys.exit(1)

# Project imports
from log import log, statLog
from ifcontrollers import PanController
from cmapDraw import CMapDraw
from timeFunc import print_timing, tic, toc
from helpViewer import HelpViewer
from progressBox import ProgressBox
log.debug('Loaded...')


class CMapConverter(object):
    '''Standard application interface. Just a board where to write questions
    '''
   
    # Methods called during initialization
    def __init__(self, wTree, mapper, win, responseLogger):
        
        # Remembers the mapper and logger
        self.mapper = mapper
        self.responseLogger = responseLogger
        
        # Sets up basic interface
        self.cmapLayout = wTree.get_widget('cmapLayout')
        self.questionListWidget = wTree.get_widget('Questions')
        self.canvasSW = wTree.get_widget('canvasSW')
        self.infoBox = wTree.get_widget('infoBoxContent')
        self.infoBoxStandard = wTree.get_widget('standardLabel')
        self.showOnlySelected = wTree.get_widget('showOnlySelected')
        self.questionsScroll = wTree.get_widget('questionsScroll')
        
        dic = {
               'on_showOnlySelected__toggled' : self.hideOrShowSelected,
               }
        wTree.signal_autoconnect(dic)
        
        # The canvas for the CMap
        self.canvas = CMapDraw(self.canvasSW, mapper.cmap.graph, self)
        self.canvas.connect('selection-changed', self.selectionChanged)
        self.canvas.connect('show-actions-menu', self.showActionsMenu)
        
        # The questions list
        self.questions = []
        
        # The list of focused elements
        self.focused = []
        self.oldFocused = []
        gobject.timeout_add(200, self.processHighlightStack)
        
        # The list of selected elements
        self.selected = []
        self.lastPosition = 0 # Will be used to remember the position before the
                              #  selection
        
        # The help interface
        self.help = HelpViewer()
        
        # The progress box
        self.progressBox = ProgressBox(win)
        
        # Shows everyone
        self.cmapLayout.show_all()
    
        self.update()
    
    def close(self):
        'Closes this cmap edition'
        self.cmapLayout.hide_all()
        self.mapper.clearDB()
    
    def getFocus(self):
        'Return the question that is focused'
        return self.focused
    
    def setFocus(self, question):
        'Put the focus on the question'
        self.oldFocused = self.focused
        self.focused = question
    focus = property(getFocus, setFocus)

    def addQuestion(self, question):
        'Adds a question to the question list '

        question.build()
        if question not in self.questions:
            question.responseLogger = self.responseLogger
            # Puts the question widget in the list
            question.owner = self
            question.connect('changed', self.update)
            self.questionListWidget.pack_start(question, expand=False)
            self.cmapLayout.show_all()
        
            self.questions.append(question)
        
    def rebuildQuestionList(self):
        'Tell the questions to recalculate'
        
        self.progressBox.show()
        self.progressBox.mainLabel = 'Updating questions'
        self.progressBox.secundaryLabel = '%d questions to be updated' % \
                                        len(self.questions)
        self.progressBox.setSizeHint(len(self.questions))
        
        tUpdate = tic()
        # Update the data in each question
        for question in self.questions:
            question.update()
            self.progressBox.pulse()
        elapsed = toc(tUpdate)
        statLog.info('Updated questions in %0.6f s \n' % elapsed )

        newQuestions = self.mapper.getQuestions(self.progressBox)
        
        self.progressBox.show()
        self.progressBox.mainLabel = 'Adding new questions'
        self.progressBox.secundaryLabel = ('%d possible questions to be added'
                                            % len(newQuestions))
        self.progressBox.setSizeHint(len(newQuestions))
        
        tAdd = tic()
        # Update the data in each question
        log.debug('Adding %d new questions', len(newQuestions))
        for question in newQuestions:
            self.addQuestion(question)
            self.progressBox.pulse()
        elapsed = toc(tAdd)
        
        tTotal = toc(tUpdate)
        statLog.info('Questions updated in %0.6f s' % tTotal)

        total, active, answered = self.getQuestionsStatistics()
        statLog.info('Application has %d/%d/%d (total/active/answered) '
                     'questions' % (total, active, answered))
        print('-----------------------------------------------------------'
              '\n\n\n')
        
        self.progressBox.hide()

    def getQuestionsStatistics(self):
        '''Returns a triple (total, active, answered, insensitive) of all
           questions currently in the interface, where:

           - total: overall number of questions
           - active: number of questions that the user can answer
           - answered: number of questions the user has explicitly answered

           The difference is the number of questions that were automatically
           answered.
        '''

        total = len(self.questions)
        active = sum(1 for q in self.questions if q.sensitive)
        answered = sum(1 for q in self.questions if q.buttonPressed)
        inactive = sum(1 for q in self.questions 
                       if q.sensitive == False and q.buttonPressed == False)

        if total != (inactive + answered + active):
            log.error('The question count does not add up: %d/%d/%d/%d '
                      '(total, active, answered, inactive)' %
                      (total, active, answered, inactive)
                )
        return (total, active, answered)
    
    def checkDuplicatedQuestions(self):
        'Checks if no two questions are the same. If they are, remove one'
        for question in self.questions:
            if self.questions.count(question) > 1:
                self.questionListWidget.remove(question)
                self.questions.remove(question)
        
    def update(self, widget = None, undo = False):
        'Calls every method that is necessary for an update'
        self.rebuildQuestionList()
        if undo:
            self.checkDuplicatedQuestions()
        self.hideOrShowSelected()
        self.setInfoBox()

    def processHighlightStack(self):
        'Process the stack of highlight questions'

        if self.focused != self.oldFocused:
            self.oldFocused = self.focused
            self.canvas.highlighted = self.focused
            self.canvas.redraw_canvas()

        return True
    
    def getIsSelected(self):
        '''Return if we should show only the selected elements'''
        return self.showOnlySelected.get_active()
    isShowOnlySelected = property(getIsSelected)
    
    # callbacks
    def hideOrShowSelected(self, widget = None):
        'Hides or shows the questions about the selected elements'
        selected = set(self.selected)
        if self.isShowOnlySelected and len(selected) > 0:
            if self.lastPosition == 0: # There was nothing selected
                self.lastPosition = self.questionsScroll. \
                                            get_vadjustment().get_value()
                self.questionsScroll.get_vadjustment().set_value(0)
            for question in self.questions:
                if set(question.toHighlight) & selected == set():
                    question.hide_all()
                else:
                    question.show_all()
        else:
            for question in self.questions:
                question.show_all()
            
            while gtk.events_pending():
                gtk.main_iteration(False)
            if self.lastPosition != 0:
                self.questionsScroll.get_vadjustment().set_value(
                                                         self.lastPosition)
                self.lastPosition = 0
    
    def selectionChanged(self, widget, selectedElements):
        'Sets the elements that are selected right now'
        
        self.selected = selectedElements
        self.hideOrShowSelected(widget)
        self.setInfoBox()

        return True
        
    def setInfoBox(self):
        'Changes the widget that is displayed in the infoBox'
        
        for child in self.infoBox:
            self.infoBox.remove(child)
        if self.selected == None or self.selected == []:
            self.infoBox.pack_start(self.infoBoxStandard)
        else:
            for element in self.selected:
                self.infoBox.pack_start(self.mapper.getInfoBox(element))
    
    def showActionsMenu(self, widget, event):
        'Shows the actions menu for that element'
        
        menu = gtk.Menu()
        for element in self.selected:
            for action in self.mapper.getActions(element):
                action.connect('on-answer',
                               lambda widget: self.update())
                menu.append(action)
                
        menu.show_all()
        menu.popup(None, None, None, event.button, event.time)
    
    def centerOnHighlight(self, *var):
        'Center the screen on the highlighted elements'
        
        (x, y) = self.canvas.getHighlightedCG()
        if x == -1 or y == -1: # No elements selected
            return
        
        allocation = self.canvasSW.get_allocation()
        width = allocation.width
        height = allocation.height
        
        hAdjust = self.canvasSW.get_hadjustment()
        vAdjust = self.canvasSW.get_vadjustment()

        # An adjust value gives the position of the topmost or leftmost pixel
        # (depending if it is a vertical or horizontal adjust), so it is
        # necessary to know the display area size to set the position correctly
        validRange = hAdjust.upper - hAdjust.lower - width
        pos = min(x - width/2, validRange)
        hAdjust.set_value(pos)
        
        validRange = vAdjust.upper - vAdjust.lower - height
        pos = min(y - height/2, validRange)
        vAdjust.set_value(pos)
    
    
